import logging
from threading import Thread, RLock, Condition
import time
from .app import Equipment


class ECG(Equipment):
    """
    心电仪, 心电仪是这样一种对象，如果超过指定时间它的pulse()函数没有被执行，则会报警。
    心电仪常被用来监测一个进程或者一个对象的运行情况，后者必须在指定的时间内————比如每20秒————
    调用一次心电仪的pulse()函数，否则就会被认为超时，事先指定的报警函数就会被调用。
    """
    def __init__(self, timeout=20, alert_func=None, args=None, kwargs=None, ignore_first=False, keep_running=True):
        super(ECG, self).__init__()
        self.timeout = timeout
        self.ignore_first = ignore_first
        self.keep_running = keep_running
        self.alert_func = alert_func
        self.args = [] if args is None else args
        self.kwargs = {} if kwargs is None else kwargs
        self._wait = Condition(self.lock)
        self._new_pulse = False
        self.first = True

    def _start(self):
        thread = Thread(target=self._run, daemon=True)
        thread.start()

    def _stop(self):
        with self._wait:
            self._wait.notify_all()

    def pulse(self):
        with self._wait:
            self._new_pulse = True

    def _run(self):
        self.first = True
        with self._wait:
            while True:
                self._new_pulse = False
                self._wait.wait(self.timeout)
                if self.stopping or self.status == 'stop':
                    break
                if self.status == 'running':
                    if not (self._new_pulse or self.first and self.ignore_first):
                        self.alert()
                        if not self.keep_running:
                            break
                    if self._new_pulse:
                        self.first = False

    def alert(self):
        try:
            self.alert_func(*self.args, **self.kwargs)
        except Exception as e:
            logging.getLogger('ECG').error(e)


class Clock(Equipment):
    """
    Clock，闹钟对象。每到指定的时间，例如每天11:30，事先指定的函数就会被执行。
    """
    def __init__(self):
        super(Clock, self).__init__()
        self._wait = Condition(RLock())
        self._tasks = []

    def add_task(self, func, start_time=None, duration=None, end_time=None, args=None, kwargs=None):
        """
        增加一个任务。
        :param func 被执行的函数
        :param start_time Time类型的任务起始时间, None表示马上开始
        :param duration Time类型的任务间隔时间，缺省值None表示任务只发生一次，不会重复
        :param end_time Time类型的任务终止时间，任务最后一次的执行时间不会大于该时间。缺省值None表示任务永不终止
        :param args 执行任务函数func时所带的位置参数(Positional Arguments，又称为可变参数)，缺省值None表示没有位置参数
        :param kwargs 执行任务函数func时所带的关键字参数(Keyword Arguments，又称为有名参数)，缺省值None表示没有有名参数
        """
        if start_time is None:
            start_time = now()
        else:
            _fill_time(start_time)
        if end_time is not None:
            _fill_time(end_time)
        task = Task(func=func, start_time=start_time, duration=duration, end_time=end_time, args=args, kwargs=kwargs)
        with self._wait:
            self._add_task(task)
            self._wait.notify_all()

    def _add_task(self, task):
        i = len(self._tasks) - 1
        while i >= 0:
            w = self._tasks[i]
            if task >= w:
                break
            i -= 1
        self._tasks.insert(i + 1, task)

    def _start(self):
        thread = Thread(target=self._run, daemon=True)
        thread.start()

    def _stop(self):
        with self._wait:
            self._wait.notify_all()

    def _run(self):
        with self._wait:
            while not self.stopping:
                seconds = self._next_wait_seconds()
                if seconds is None:
                    break
                if seconds > 0:
                    self._wait.wait(seconds)

    def _next_wait_seconds(self):
        now = get_time()
        old_tasks = []
        new_tasks = []
        while len(self._tasks) > 0:
            task = self._tasks[0]
            if task.start_time <= now:
                old_tasks.append(task)
                self._tasks.pop(0)
            else:
                break
            next_time = task.next_time(now)
            if next_time is not None:
                new_tasks.append(task)
        for task in new_tasks:
            self._add_task(task)

        ths = [Thread(target=t.do, daemon=True) for t in old_tasks]
        for th in ths:
            th.start()
        if len(self._tasks) == 0:
            return None
        return now.get_seconds_to(self._tasks[0].start_time)


names = ('year', 'month', 'day', 'weeks', 'hour', 'minute', 'second')


class Time:
    """
    时间轴上的一个点，点和点相加减可以得到另一个点
    """
    def __init__(self, year=0, month=0, day=0, weeks=0, hour=0, minute=0, second=0):
        self.second = second % 60
        minute += second // 60
        self.minute = minute % 60
        hour += minute // 60
        self.hour = hour % 24
        day += hour // 24 + weeks * 7

        while True:
            m_days = month_days(month, year)
            if day > m_days:
                day -= m_days
                month += 1
                if month == 12:
                    month = 1
                    year += 1
            else:
                break

        self.year = year
        self.month = month
        self.day = day
        self.weeks = 0

    def get_seconds_to(self, time):
        return time.to_seconds() - self.to_seconds()

    def to_seconds(self):
        seconds = (self.year - 1) * 365.2425
        days = self.day - 1
        for m in range(1, self.month):
            days += month_days(m, self.year)
        seconds += days * 24 * 60 * 60
        return (self.hour * 60 + self.minute) * 60 + self.second

    def __repr__(self):
        return '%d-%02d-%02d %02d:%02d:%02d w(%d)' % \
               (self.year, self.month, self.day, self.hour, self.minute, self.second, self.weeks)

    def __add__(self, other):
        other = get_time(other)
        result = {}
        for name in names:
            if getattr(self, name) is None:
                if getattr(other, name) is None:
                    value = None
                else:
                    value = getattr(other, name)
            else:
                if getattr(other, name) is None:
                    value = getattr(self, name)
                else:
                    value = getattr(self, name) + getattr(other, name)
            result[name] = value
        return Time(**result)

    def __eq__(self, other):
        other = get_time(other)
        return self.year == other.year and self.month == other.month and self.day == other.day and \
               self.hour == other.hour and self.minute == other.minute and self.second == other.second

    def __lt__(self, other):
        return self.year < other.year or (self.year == other.year and \
               (self.month < other.month or self.month == other.month and \
               (self.day < other.day or self.day == other.day and \
               (self.hour < other.hour or self.hour == other.hour and \
               (self.minute < other.minute or self.minute == other.minute and \
               self.second < other.second)))))

    def __gt__(self, other):
        return self.year > other.year or self.year == other.year and \
               self.month > other.month or self.month == other.month and \
               self.day > other.day or self.day == other.day and \
               self.hour > other.hour or self.hour == other.hour and \
               self.month > other.month or self.month == other.month and \
               self.second > other.second or self.second == other.second

    def __le__(self, other):
        return self.__eq__(other) or self.__lt__(other)

    def __ge__(self, other):
        return self.__eq__(other) or self.__gt__(other)

    def __ne__(self, other):
        return not self.__eq__(other)


def now():
    t = time.localtime(time.time())
    return Time(year=t.tm_year, month=t.tm_mon, day=t.tm_mday, weeks=0,
                hour=t.tm_hour, minute=t.tm_min, second=t.tm_sec)


def _fill_time(t: Time):
    nw = now()
    if t.year == 0:
        t.year = nw.year
    if t.month == 0:
        t.month = nw.month
    if t.day == 0:
        t.day = nw.day


def get_time(value=None):
    if value is None:
        return now()
    if isinstance(value, Time):
        return value
    t = type(value)
    if t == int:
        return Time(day=value)
    if t in (list, tuple):
        if len(value) == 3:
            year, month, day = value
            return Time(year=year, month=month, day=day)
        if len(value) == 6:
            year, month, day, hour, minute, second = value
            return Time(year=year, month=month, day=day, hour=hour, minute=minute, second=second)
    raise AssertionError('不能把%s转成一个Time' % value)


def month_days(month, year):
    if month == 2:
        if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
            m_days = 29
        else:
            m_days = 28
    elif month in (4, 6, 9, 11):
        m_days = 30
    else:
        m_days = 31
    return m_days


class Task:
    def __init__(self, func, start_time: Time, duration: Time = None,
                 end_time: Time = None, args=None, kwargs=None):
        self.func = func
        self.args = [] if args is None else args
        self.kwargs = {} if kwargs is None else kwargs
        self.start_time = start_time
        self.duration = duration
        self.end_time = end_time

    def do(self):
        try:
            self.func(*self.args, **self.kwargs)
        except Exception as e:
            logging.getLogger().error(e)

    def next_time(self, now):
        """下次运行的时间点"""
        if self.duration is None:
            self.start_time = None
        else:
            while self.start_time <= now:
                self.start_time += self.duration
            if self.end_time is not None and self.start_time > self.end_time:
                self.start_time = None
        return self.start_time

    def has_next_time(self):
        if self.duration is None:
            return False
        if self.end is None:
            return True
        return self.start_time <= self.end


